using System.Collections.Generic;
using System.Data.Objects;
using System.Linq.Expressions;
using System.Web;
using System;
using System.Linq;
using System.Data.Objects.DataClasses;
using System.Data;
using System.Threading;
using System.Reflection;

namespace MvcCms.Data.Code
{
    public class EntitiesProvider : IEntitiesProvider
    {
        private static MvcCmsEntities _mvccmsEntities;

        private static MvcCmsEntities MvcCmsEntities
        {
            get
            {
                if (HttpContext.Current != null)
                {
                    var objectContextKey = HttpContext.Current.GetHashCode().ToString();
                    if (!HttpContext.Current.Items.Contains(objectContextKey))
                    {
                        try
                        {
                            HttpContext.Current.Items.Add(objectContextKey, new MvcCmsEntities());
                        }
                        catch {}
                    }
                    _mvccmsEntities = HttpContext.Current.Items[objectContextKey] as MvcCmsEntities;
                    return _mvccmsEntities;
                }
                else
                {
                    //use a thread this is not a web request
                    var key = "__WRSCDC_" + Thread.CurrentContext.ContextID.ToString();
                    var threadData = Thread.GetNamedDataSlot(key);
                    object context = null;
                    if (threadData != null)
                    {
                        context = Thread.GetData(threadData);
                    }

                    if (context == null)
                    {
                        try
                        {
                            context = new MvcCmsEntities();
                        }
                        catch {}
                        if (context != null)
                        {
                            if (threadData == null)
                            {
                                threadData = Thread.AllocateNamedDataSlot(key);
                            }
                            Thread.SetData(threadData, context);
                        }
                    }
                    return Thread.GetData(threadData) as MvcCmsEntities;
                }
            }
        }

        #region IEntitiesProvider Members

        public MvcCmsEntities GetEntityContext()
        {
            return MvcCmsEntities;
        }

        public void ReopenConnection()
        {
            string connectionString = _mvccmsEntities.Connection.ConnectionString;
            _mvccmsEntities.Connection.Dispose();
            _mvccmsEntities.Connection.Close();
            _mvccmsEntities.Connection.ConnectionString = connectionString;
            _mvccmsEntities.Connection.Open();
        }
        public void Detach<TEntity>(TEntity entity)
            where TEntity : class
        {
            if (!IsDetached(entity))
            {
                _mvccmsEntities.Detach(entity);
            }
        }
        public void Attach<TEntity>(TEntity entity)
            where TEntity : class,IEntityWithKey
        {
            if (!IsAttached(entity))
            {
                _mvccmsEntities.Attach(entity);
            }
        }
        private static bool IsDetached<TEntity>(TEntity entity) where TEntity : class
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            ObjectStateEntry entry;
            if (_mvccmsEntities.ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
            {
                return (entry.State == EntityState.Detached);
            }
            return false;
        }
        private static bool IsAttached<TEntity>(TEntity entity) where TEntity : class, IEntityWithKey
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            ObjectStateEntry entry;
            if (_mvccmsEntities.ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
            {
                return (entry.State != EntityState.Detached);
            }
            return false;
        }
        public void Refresh<TEntity>(ObjectContext context, RefreshMode refreshMode, TEntity entity)
            where TEntity : class
        {
            context.Refresh(refreshMode, entity);
        }

        public void Refresh<TEntity>(ObjectContext context, RefreshMode refreshMode, IEnumerable<TEntity> entities)
            where TEntity : class
        {
            context.Refresh(refreshMode, entities);
        }

        #endregion
    }

    public static class QueryableExtensions
    {
        private static Expression<Func<TElement, bool>> GetWhereInExpression<TElement, TValue>(Expression<Func<TElement, TValue>> propertySelector, IEnumerable<TValue> values)
        {
            ParameterExpression p = propertySelector.Parameters.Single();
            if (!values.Any())
                return e => false;

            var equals = values.Select(value => (Expression)Expression.Equal(propertySelector.Body, Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));

            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }

        /// <summary>
        /// Return the element that the specified property's value is contained in the specifiec values
        /// </summary>
        /// <typeparam name="TElement">The type of the element.</typeparam>
        /// <typeparam name="TValue">The type of the values.</typeparam>
        /// <param name="source">The source.</param>
        /// <param name="propertySelector">The property to be tested.</param>
        /// <param name="values">The accepted values of the property.</param>
        /// <returns>The accepted elements.</returns>
        public static IQueryable<TElement> WhereIn<TElement, TValue>(this IQueryable<TElement> source, Expression<Func<TElement, TValue>> propertySelector, params TValue[] values)
        {
            return source.Where(GetWhereInExpression(propertySelector, values));
        }

        /// <summary>
        /// Return the element that the specified property's value is contained in the specifiec values
        /// </summary>
        /// <typeparam name="TElement">The type of the element.</typeparam>
        /// <typeparam name="TValue">The type of the values.</typeparam>
        /// <param name="source">The source.</param>
        /// <param name="propertySelector">The property to be tested.</param>
        /// <param name="values">The accepted values of the property.</param>
        /// <returns>The accepted elements.</returns>
        public static IQueryable<TElement> WhereIn<TElement, TValue>(this IQueryable<TElement> source, Expression<Func<TElement, TValue>> propertySelector, IEnumerable<TValue> values)
        {
            return source.Where(GetWhereInExpression(propertySelector, values));
        }

        private static Expression<Func<TElement, bool>>
            GetWhereNotInExpression<TElement, TValue>(
            Expression<Func<TElement, TValue>> propertySelector,
            IEnumerable<TValue> values)
        {
            ParameterExpression p =
                propertySelector.Parameters.Single();

            if (!values.Any())
            {
                return e => true;
            }

            var unequals = values.Select(value =>
                (Expression)Expression.NotEqual(
                    propertySelector.Body,
                    Expression.Constant(value, typeof(TValue))
                )
            );

            var body = unequals.Aggregate<Expression>(
                (accumulate, unequal) => Expression.And(accumulate, unequal));

            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }

        public static IQueryable<TElement>
            WhereNotIn<TElement, TValue>(this IQueryable<TElement> source,
            Expression<Func<TElement, TValue>> propertySelector,
            params TValue[] values)
        {
            return source.Where(GetWhereNotInExpression(
                propertySelector, values));
        }

        public static IQueryable<TElement>
            WhereNotIn<TElement, TValue>(this IQueryable<TElement> source,
            Expression<Func<TElement, TValue>> propertySelector,
            IEnumerable<TValue> values)
        {
            return source.Where(GetWhereNotInExpression(
                propertySelector, values));
        }

    }
}